#include "RLFEngine.h"
#include <string.h>

using namespace std;

RLFEngine::RLFEngine(int* inputPixels, int pixelsLength, int imageWidth, int sampleSizeX, int sampleSizeY)
{
	this->imageWidth = imageWidth;
	this->pixelsLength = pixelsLength;
	this->imageHeight = pixelsLength / imageWidth;

	this->sampleSizeX = sampleSizeX;
	this->sampleSizeY = sampleSizeY;
	
	this->pixels = new int[pixelsLength];
	memcpy((void*)this->pixels, (void*)inputPixels, pixelsLength*sizeof(int));

	createOutputArrays();
	this->counter = new PrimitivesCounter(this->pixels, imageWidth);
}

RLFEngine::~RLFEngine(void)
{
	for(int i=0; i<RLFEngine::outputImageCount; i++)
	{
		delete(this->outputPixels[i]);
	}
	delete(this->outputPixels);
	delete(this->pixels);
}

void RLFEngine::calculateStatistics(PrimitiveDirection direction, int windowSizeX, int windowSizeY)
{
	int outputX, outputY=0;

	for(int sampleY=0; sampleY < this->imageHeight; sampleY+=this->sampleSizeY)
	{
		// making sure, that we won't step out of image bounds
		int sampleYEnd = sampleY + this->sampleSizeY;
		(sampleYEnd > this->imageHeight) ? sampleYEnd = this->imageHeight : NULL;

		int sampleCenterY = this->sampleSizeY/2 + sampleY;
		int windowRadiusY = windowSizeY /2;
		int windowY = sampleCenterY-windowRadiusY;
		int windowYEnd = sampleCenterY+windowRadiusY;
		(windowY < 0) ? windowY = 0 : NULL;
		(windowY > this->imageHeight-1) ? windowY = sampleY : NULL;
		(windowSizeY % 2 != 0) ? windowYEnd++ : NULL;	// eg. window has 3 width, then radius is 1 but difference between first and last index needs to be 4
		(windowYEnd > this->imageHeight) ? windowYEnd = this->imageHeight : NULL;

		outputX=0;
		for(int sampleX = 0; sampleX < this->imageWidth; sampleX+=this->sampleSizeX)
		{
			int sampleXEnd = sampleX + this->sampleSizeX;
			(sampleXEnd > this->imageWidth) ? sampleXEnd = this->imageWidth : NULL;
		
			int windowRadiusX = windowSizeX /2;			
			int sampleCenterX = this->sampleSizeX/2 + sampleX;			
			int windowX = sampleCenterX-windowRadiusX;
			int windowXEnd = sampleCenterX+windowRadiusX;
			(windowX < 0) ? windowX = 0 : NULL;
			(windowX > this->imageWidth-1) ? windowX = sampleX : NULL;
			(windowSizeX % 2 != 0) ? windowXEnd++ : NULL;
			(windowXEnd > this->imageWidth) ? windowXEnd = this->imageWidth : NULL;			

			// counting primitives for the window
			unordered_map <int, unordered_map <int, int>*> primitives1, primitives2;
			int primitivesCount = this->counter->countPrimitives(direction, &primitives1, &primitives2, windowX, windowXEnd, windowY, windowYEnd);
			
			int windowValue = calculateWindowSPE(&primitives1, primitivesCount, windowX, windowXEnd, windowY, windowYEnd, direction);
			int outputArrayIndex = getImageIndex(direction, RunLengthStatistic::ShortPrimitiveEmphasis);
			// setting the pixel in the output image
			this->outputPixels[outputArrayIndex][outputY*this->outputImageWidth+outputX] = windowValue;

			windowValue = calculateWindowLPE(&primitives1, primitivesCount, windowX, windowXEnd, windowY, windowYEnd, direction);
			outputArrayIndex = getImageIndex(direction, RunLengthStatistic::LongPrimitiveEmphasis);
			this->outputPixels[outputArrayIndex][outputY*this->outputImageWidth+outputX] = windowValue;

			windowValue = calculateWindowGLU(&primitives1, primitivesCount, windowX, windowXEnd, windowY, windowYEnd);
			outputArrayIndex = getImageIndex(direction, RunLengthStatistic::GrayLevelUniformity);
			this->outputPixels[outputArrayIndex][outputY*this->outputImageWidth+outputX] = windowValue;

			windowValue = calculateWindowPLU(&primitives2, primitivesCount, windowX, windowXEnd, windowY, windowYEnd);
			outputArrayIndex = getImageIndex(direction, RunLengthStatistic::PrimitiveLengthUniformity);
			this->outputPixels[outputArrayIndex][outputY*this->outputImageWidth+outputX] = windowValue;

			windowValue = calculateWindowPP(&primitives1, primitivesCount, windowX, windowXEnd, windowY, windowYEnd, direction);
			outputArrayIndex = getImageIndex(direction, RunLengthStatistic::PrimitivePercentage);
			this->outputPixels[outputArrayIndex][outputY*this->outputImageWidth+outputX] = windowValue;

			this->counter->destroyPrimitives(&primitives1);
			this->counter->destroyPrimitives(&primitives2);

			outputX++;
		}
		outputY++;
	}
}

int* RLFEngine::getRLFImage(PrimitiveDirection direction, RunLengthStatistic statisctic)
{
	int index = getImageIndex(direction, statisctic);
	return outputPixels[index];
}

int RLFEngine::getOutputPixelCount()
{
	return this->outputPixelsLength;
}

void RLFEngine::createOutputArrays()
{
	this->outputImageWidth = this->imageWidth / this->sampleSizeX;
	if(this->imageWidth % this->sampleSizeX != 0)	// one additional pixel of size needed
		this->outputImageWidth++;
	
	this->outputImageHeight = this->imageHeight / this->sampleSizeY;
	if(this->imageHeight % this->sampleSizeY != 0)
		this->outputImageHeight++;

	this->outputPixelsLength = this->outputImageWidth * this->outputImageHeight;

	this->outputPixels = new int*[RLFEngine::outputImageCount];
	for(int i=0; i<RLFEngine::outputImageCount; i++)
	{
		this->outputPixels[i] = new int[this->outputPixelsLength];
	}
}

int RLFEngine::calculateWindowSPE(unordered_map <int, unordered_map <int, int>*>* primitives, int primitivesCount, int xStart, int xEnd, int yStart, int yEnd, PrimitiveDirection direction)
{
	double max = 1.0;
	double maxR = getMaxPrimitiveLength(direction, xStart, xEnd, yStart, yEnd);
	double min = 1.0/ (maxR*maxR);
	
	double SPEValue = 0.0;
	double primitiveValue;

	for ( auto colorIterator = primitives->begin(); colorIterator != primitives->end(); ++colorIterator )
	{
		for(auto lengthIterator = colorIterator->second->begin(); lengthIterator != colorIterator->second->end(); ++lengthIterator )
		{
			double count = lengthIterator->second;
			int length = lengthIterator->first;
			
			primitiveValue = count / (double)(length*length);
			SPEValue += primitiveValue;
		}
	}
	
	SPEValue /= (double)primitivesCount;
	SPEValue = scaleToColor(SPEValue, min, max);
	return (int)SPEValue;
}

int RLFEngine::calculateWindowLPE(unordered_map <int, unordered_map <int, int>*>* primitives, int primitivesCount, int xStart, int xEnd, int yStart, int yEnd, PrimitiveDirection direction)
{
	double min = 1.0;
	double maxR = getMaxPrimitiveLength(direction, xStart, xEnd, yStart, yEnd);
	double max = maxR*maxR;
	
	double LPEValue= 0.0;
	double primitiveValue;
	
	for ( auto colorIterator = primitives->begin(); colorIterator != primitives->end(); ++colorIterator )
	{
		for(auto lengthIterator = colorIterator->second->begin(); lengthIterator != colorIterator->second->end(); ++lengthIterator )
		{
			double count = lengthIterator->second;
			int length = lengthIterator->first;
			
			primitiveValue = count * (double)(length*length);
			LPEValue += primitiveValue;
		}
	}
	
	LPEValue /= (double)primitivesCount;
	LPEValue = scaleToColor(LPEValue, min, max);
	return (int)LPEValue;
}

int RLFEngine::calculateWindowGLU(unordered_map <int, unordered_map <int, int>*>* primitives, int primitivesCount, int xStart, int xEnd, int yStart, int yEnd)
{
	double min = 1.0;
	double max = (double)primitivesCount;
	double GLUValue= 0.0;
	double subSum;
	
	for ( auto colorIterator = primitives->begin(); colorIterator != primitives->end(); ++colorIterator )
	{
		subSum = 0.0;
		for(auto lengthIterator = colorIterator->second->begin(); lengthIterator != colorIterator->second->end(); ++lengthIterator )
		{
			subSum += (double) lengthIterator->second;
		}
		GLUValue += subSum * subSum;
	}
	
	GLUValue /= (double)primitivesCount;
	GLUValue = scaleToColor(GLUValue, min, max);	
	return (int)GLUValue;
}

int RLFEngine::calculateWindowPLU(unordered_map <int, unordered_map <int, int>*>* primitives, int primitivesCount, int xStart, int xEnd, int yStart, int yEnd)
{
	double min = 1.0;
	double max = (double)primitivesCount;
	double PLUValue= 0.0;
	double subSum;
	
	for ( auto lengthIterator = primitives->begin(); lengthIterator != primitives->end(); ++lengthIterator )
	{
		subSum = 0.0;
		for(auto colorIterator = lengthIterator->second->begin(); colorIterator != lengthIterator->second->end(); ++colorIterator )
		{
			subSum += (double) colorIterator->second;
		}
		PLUValue += subSum * subSum;
	}
	
	PLUValue /= (double)primitivesCount;
	PLUValue = scaleToColor(PLUValue, min, max);	
	return (int)PLUValue;
}

int RLFEngine::calculateWindowPP(unordered_map <int, unordered_map <int, int>*>* primitives, int primitivesCount, int xStart, int xEnd, int yStart, int yEnd, PrimitiveDirection direction)
{
	double min, max = 1.0;
	if(direction == PrimitiveDirection::Horizontal)
		min = 1.0/(double)(xEnd - xStart);
	else if(direction == PrimitiveDirection::Vertical)
		min = 1.0/(double)(yEnd - yStart);
	else
	{
		max = 2.0;
		/*double maxR;
		((xEnd - xStart) > (yEnd - yStart)) ? maxR = (double)(xEnd - xStart) : maxR = (double)(yEnd - yStart);
		min = 1.0 + 1.0/maxR;*/
		double xSpan = xEnd - xStart;
		double ySpan = yEnd - yStart;
		min = (xSpan + ySpan) / (xSpan * ySpan);
	}

	double PPValue= (double)primitivesCount;
	PPValue /= (double)((xEnd - xStart) * (yEnd - yStart));
	PPValue = scaleToColor(PPValue, min, max);	
	return (int) PPValue;
}

int RLFEngine::getImageIndex(PrimitiveDirection direction, RunLengthStatistic statisctic)
{
	int index = 0;
	switch(direction)
	{
	case PrimitiveDirection::Horizontal:
		index = 0;
		break;
	case PrimitiveDirection::Vertical:
		index = 5;
		break;
	case PrimitiveDirection::Both:
		index=10;
		break;
	default:
		throw new exception("getImageIndex failed because direction was unrecognized");
	}

	switch(statisctic)
	{
	case RunLengthStatistic::ShortPrimitiveEmphasis:
		index+=0;
		break;
	case RunLengthStatistic::LongPrimitiveEmphasis:
		index+=1;
		break;
	case RunLengthStatistic::GrayLevelUniformity:
		index+=2;
		break;
	case RunLengthStatistic::PrimitiveLengthUniformity:
		index+=3;
		break;
	case RunLengthStatistic::PrimitivePercentage:
		index+=4;
		break;
	default:
		throw new exception("getImageIndex failed because statistic was unrecognized");
	}

	return index;
}

double RLFEngine::scaleToColor(double value, double min, double max)
{
	double targetRange = 255;
	double givenRange = max - min;
	
	// preventing division by zero and the outcome of unprecise floating point operations
	if(min >= max)
		return min;

	double multiplier = targetRange / givenRange;
	double offset = 0 - (min * multiplier);

	return value*multiplier + offset;
}

double RLFEngine::getMaxPrimitiveLength(PrimitiveDirection direction, int xStart, int xEnd, int yStart, int yEnd)
{
	double r; 
	if(direction == PrimitiveDirection::Horizontal)
		r = (double)(xEnd - xStart);
	else if(direction == PrimitiveDirection::Vertical)
		r = (double)(yEnd - yStart);
	else
	{
		((xEnd - xStart) > (yEnd - yStart)) ? r = (double)(xEnd - xStart) : r = (double)(yEnd - yStart);
	}
	return r;
}
